
package Engine.Character;

import javax.microedition.lcdui.Image;
import javax.microedition.lcdui.game.Sprite;

/**
 * Abstract class containing common variables and methods of Characters. Subclasses are only used when a battle starts, called by the current
 * BattleManager object at GameAdmin. The class handles everything regarding the access to the Character's variables and keeping them in range
 * (HP, MP), elemental multiplier calculation and damage calculation.
 * Subclasses reference the user's players and the enemies that will be fought in every battle.
 * Leveling: y = ((lnx)(x)+5)(5)
 * Damage calculation: (multiplier)[(attack)(level)] - [(def)(level)]
 * @author Gerardo Galíndez
 */
public abstract class Character extends Sprite {

    //---------------------------------------------------------
    //Character status variables-------------------------------
    private int maxHealthPoints;
    private int currentHealthPoints;
    private int maxMagicPoints;
    private int currentMagicPoints;
    private int currentLevel;
    private int physicalAttack;
    private int physicalDefense;
    private int magicalAttack;
    private int magicalDefense;
    private int damageDealt;
    private final int ELEMENTAL_ATTRIBUTE;
    //Elemental index constants--------------------------------
    /**
     * Character integer index representing the Fire element.
     */
    public static final int FIRE = 0;
    /**
     * Character integer index representing the Earth element.
     */
    public static final int EARTH = 1;
    /**
     * Character integer index representing the Wind element.
     */
    public static final int WIND = 2;
    /**
     * Character integer index representing the Water element.
     */
    public static final int WATER = 3;
    //---------------------------------------------------------

    //<<-------------------------------------------------------Object management------------------------------------------------------->>
    /**
     * Creates a Character type object using an image, its respective frame dimensions and an elemental constant.
     * @param characterImage Image passed to the Sprite constructor.
     * @param frameWidth Width of each frame of the corresponding image.
     * @param frameHeight Height of each frame of the corresponding image.
     * @param element Character integer index constant representing an elemental type.
     */
    public Character(Image characterImage, int frameWidth, int frameHeight, int element) {
        super(characterImage, frameWidth, frameHeight);
        this.ELEMENTAL_ATTRIBUTE = element;
        this.maxHealthPoints = 100;
        this.maxMagicPoints = 100;
        this.currentHealthPoints = 50;
        this.currentMagicPoints = 30;
        this.currentLevel = 1;
    }
    //<<------------------------------------------------------------------------------------------------------------------------------->>

    //<<-------------------------------------------------------Damage calculation------------------------------------------------------>>

    
    /**
     * Calculates the damage dealt to the current character. The method receives the attacker's element, attack and level and calculates the
     * damage using the formula: (multiplier)[(attack)(level)] - [(def)(level)]
     * @param attackerElement Attacker's elemental type
     * @param attackerAttack Attacker's physical/magical attack value
     * @param attackerLevel Attacker's level
     */
    public void calculateDamage(int attackerElement, int attackerAttack, int attackerLevel) {
        /*
         * REFACTOR COMMENTS
         * The method needs to be tested, in order to check that the damage dealt is not too small or too big.
         */
        //(multiplier)[(attack)(level)] - [(def)(level)]
        //Unclean method. Needs testing.
        damageDealt = (int)(this.calculateElementalMultiplier(attackerElement)*(attackerAttack*attackerLevel))-(physicalDefense*currentLevel);
        if(damageDealt > 0) {
            this.setCurrentHealthPoints(-damageDealt);
        } else if(damageDealt == 0) {
            this.setCurrentHealthPoints(-attackerAttack);
        } else {
            this.setCurrentHealthPoints(-1);
        }
    }

    /**
     * Compares the Character's element with the attacker's element to determine if the attack's value should be doubled or halfed, if the
     * current Character is strong or weak against the attacker.
     * @param attackerElement Character's integer index representing the attacker's element.
     * @return Multiplier's value.
     */
    public double calculateElementalMultiplier(int attackerElement) {
        switch (ELEMENTAL_ATTRIBUTE) {
            case FIRE:
                return calculateFireMultiplier(attackerElement);
            case EARTH:
                return calculateEarthMultiplier(attackerElement);
            case WIND:
                return calculateWindMultiplier(attackerElement);
            case WATER:
                return calculateWaterMultiplier(attackerElement);
            default:
                System.out.println("Bad elemental multiplier index.");
                return 0;
        }
    }

    // Calculates the fire multiplier for the attacks
    private double calculateFireMultiplier(int attackerElement) {
        switch (attackerElement) {
            case EARTH:
                return 0.5;
            case WATER:
                return 2;
            default:
                return 1;
        }
    }

    // Calculates the earth multiplier for the attacks
    private double calculateEarthMultiplier(int attackerElement) {
        switch (attackerElement) {
            case WIND:
                return 0.5;
            case FIRE:
                return 2;
            default:
                return 1;
        }
    }

    // Calculates the wind multiplier for the attacks
    private double calculateWindMultiplier(int attackerElement) {
        switch (attackerElement) {
            case WATER:
                return 0.5;
            case EARTH:
                return 2;
            default:
                return 1;
        }
    }

    // Calculates the water multiplier for the attacks
    private double calculateWaterMultiplier(int attackerElement) {
        switch (attackerElement) {
            case FIRE:
                return 0.5;
            case WIND:
                return 2;
            default:
                return 1;
        }
    }

    /**
     * Returns the magical attack of the current character.
     * @return Magical attack's value.
     */
    public int getMagicalAttack() {
        return magicalAttack;
    }

    /**
     * Returns the magical defense of the current character.
     * @return Magical defense's value.
     */
    public int getMagicalDefense() {
        return magicalDefense;
    }

    /**
     * Returns the physical attack of the current character.
     * @return Physical attack's value.
     */
    public int getPhysicalAttack() {
        return physicalAttack;
    }

    /**
     * Returns the physical defense of the current character.
     * @return Physical defense's value.
     */
    public int getPhysicalDefense() {
        return physicalDefense;
    }
    //<<------------------------------------------------------------------------------------------------------------------------------->>

    //<<--------------------------------------------------------Status methods--------------------------------------------------------->>
    /**
     * Returns the current health points of the current character.
     * @return Current health point's value.
     */
    public int getCurrentHealthPoints() {
        return currentHealthPoints;
    }

    /**
     * Adds or substracts the given number from the current health points. The method DOES NOT checkes if the health differential is to be
     * substracted or added. It relies on the sign of the integer sent.
     * @param healthDifferential Value to be added or substracted. Depends on the sign.
     */
    public void setCurrentHealthPoints(int healthDifferential) {
        this.currentHealthPoints += healthDifferential;
        if(this.currentHealthPoints > this.maxHealthPoints) {
            this.currentHealthPoints = this.maxHealthPoints;
        }
        if(this.currentHealthPoints < 0) {
            this.currentHealthPoints = 0;
        }
    }

    /**
     * Returns the current magical points of the current character.
     * @return Current magical point's value.
     */
    public int getCurrentMagicPoints() {
        return currentMagicPoints;
    }

    /**
     * Adds or substracts the given number from the current magical points. The method DOES NOT checkes if the magical differential is to be
     * substracted or added. It relies on the sign of the integer sent.
     * @param magicDifferential Value to be added or substracted. Depends on the sign.
     */
    public void setCurrentMagicPoints(int magicDifferential) {
         this.currentMagicPoints += magicDifferential;
        if (this.currentMagicPoints > this.maxMagicPoints) {
            this.currentMagicPoints = this.maxMagicPoints;
        }
        if (this.currentMagicPoints < 0) {
            this.currentMagicPoints = 0;
        }
    }

    /**
     * Returns the current level of the current character.
     * @return Current level's value.
     */
    public int getCurrentLevel() {
        return currentLevel;
    }

    /**
     * Adds a new level and calculates the new health points
     * @param levelDifferential the number of levels gain
     */
    public void setCurrentLevel(int levelDifferential){
        this.currentLevel += levelDifferential;
//        this.maxHealthPoints = this.maxHealthPoints;
//        this.maxMagicPoints = this.maxMagicPoints;
//        this.currentHealthPoints = this.maxHealthPoints;
//        this.currentMagicPoints = this.maxMagicPoints;
    }
    
    /**
     * Returns the elemental attribute of the current character.
     * @return Character's integer index representing the current character's element.
     */
    public int getElementalAttribute() {
        return ELEMENTAL_ATTRIBUTE;
    }

    /**
     * Returns the maximum health points of the current character.
     * @return Character's integer index representing the current character's maximum health points.
     */
    public int getMaxHealthPoints() {
        return maxHealthPoints;
    }



    /**
     * Sets the maximum health points of the character with the parameter healthPoints
     * @param healthPoints
     */
    public void setMaxHealthPoints(int healthPoints) {
        this.maxHealthPoints = healthPoints;
    }

    /**
     * Returns the maximum magic points of the current character.
     * @return Character's integer index representing the current character's maximum magic points.
     */
    public int getMaxMagicPoints() {
        return maxMagicPoints;
    }

    /**
     * Sets the maximum magic points of the character with the parameter magicPoints
     * @param magicPoints
     */
    public void setMaxMagicPoints(int magicPoints) {
        this.maxMagicPoints = magicPoints;
    }

    /**
     * Sets the physical attack of the character with the parameter physicalAttack
     * @param physicalAttack
     */
    public void setPhysicalAttack(int physicalAttack) {
        this.physicalAttack = physicalAttack;
    }

    /**
     * Sets the physical defense of the character with the parameter physicalDefense
     * @param physicalDefense
     */
    public void setPhysicalDefense(int physicalDefense) {
        this.physicalDefense = physicalDefense;
    }

    /**
     * Sets the magical attack of the character with the parameter magicalAttack
     * @param magicalAttack
     */
    public void setMagicalAttack(int magicalAttack) {
        this.magicalAttack = magicalAttack;
    }

    /**
     * Sets the magical defense of the character with the parameter magicalDefense
     * @param magicalDefense
     */
    public void setMagicalDefense(int magicalDefense) {
        this.magicalDefense = magicalDefense;
    }

    /**
     * Tests if the current character can keep on fighting, using its current health points.
     * @return true if health points is higher than 0, false if it's 0.
     */
    public boolean isAlive() {
        return currentHealthPoints > 0;
    }

}
